{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# Resampling Options\n",
    "\n",
    ":::{admonition} Notes\n",
    "1. Most of the options below require the [Datashader](https://datashader.org) library to be installed. The [`downsample`](option-downsample) option should preferably be used with the [tsdownsample](https://github.com/predict-idlab/tsdownsample) library installed.\n",
    "\n",
    "2. To dive deeper into the Datashader-related option, users are encouraged to read HoloViews' [Large Data user guide](https://holoviews.org/user_guide/Large_Data.html) and the [Datashader website](https://datashader.org), in particular the [Plotting pitfalls user guide](https://datashader.org/user_guide/Plotting_Pitfalls.html).\n",
    "\n",
    "3. Most of the examples below require to be run in a Jupyter notebook to experience their full interactivity (dynamic resampling after zoomin/panning).\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Performance related options for handling large datasets, including downsampling, rasterization, and dynamic plot updates:\n",
    "\n",
    "```{eval-rst}\n",
    ".. plotting-options-table:: Resampling Options\n",
    "```"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The `hvplot.sampledata.synthetic_clusters` dataset is used in many examples below. This dataset, returned as a DataFrame object, consists of five sub-datasets combined. Each of the sub-dataset has a random x, y-coordinate based on a normal distribution centered at a specific (x, y) location, with standard deviations derived from a power law, resulting in very dense to very scattered clusters. Each point also carries a `val` (`0` to `4`) and `cat` (`d1` to `d5`) column to identify its dataset and category. The total dataset contains 1,000,000 points, evenly split across the five distributions."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "print(f\"This dataset contains {len(df)} rows.\")\n",
    "print(\"Sample from each of the 5 clusters\")\n",
    "df.iloc[[int(i *len(df)) / 5 for i in range(5)]]"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-aggregator)=\n",
    "## `aggregator`\n",
    "\n",
    "The `aggregator` option determines how data is reduced when applying [`datashade=True`](option-datashade) or [`rasterize=True`](option-rasterize). Precisely, aggregators dynamically compute a single value per pixel when converting many raw data points into a rasterized image. By default, a simple `count` aggregator is applied, which creates a plot that displays how many data points are contained within each pixel.\n",
    "\n",
    "This can be particularly useful for large datasets where plotting every point is impractical. Choosing the right aggregator helps highlight the relevant aspects of the data — whether you want to count point density, show average values, or observe minimums and maximums across regions.\n",
    "\n",
    "Two main types of aggregators are available:\n",
    "- Mathematical combination of data such as the `count` of data points per pixel or the `mean` of a dimension of the supplied dataset, including: `'any'`, `'count'`, `'mode'`, `'mean'`, `'sum'`, `'var'`, `'std'`.\n",
    "- Selection of data from a dimension of the supplied dataset, or the index of the corresponding row in the dataset, including: `'first'`, `'last'`, `'min'`, `'max'`.\n",
    "\n",
    "`aggregator` accepts either:\n",
    "- A [Datashader reduction instance](https://datashader.org/api.html#reductions), such as `ds.count()` or `ds.mean('val')`.\n",
    "- A string (e.g. `'mean'`, `'count'`, `'min'`, `'max'`, etc.), in which case the aggregated dimension can be defined by setting the [`color`](option-color) option (if not, the first non-coordinate variable found is used).\n",
    "\n",
    "The `'count_cat'` or `'by'` aggregators can be used for categorical cata. `ds.by(<column>, <reduction>)` allows to define the per-category reduction function (default is `count`). Alternatively, setting the [`by`](option-by) option to a categorical column is equivalent to setting `aggregator=ds.by(<cat_column>)`.\n",
    "\n",
    "Two additional aggregators are available:\n",
    "- `ds.summary(...)` can be used to compute multiple aggregates simultaneously, by defining a sequence of key/value pairs representing the aggregate labels and reductions. For example, setting `aggregator=ds.summary(min_s=ds.min('s'), max_s=ds.max('s'))` will make both the `min_s` and `max_s` aggregated values available in the hover tooltip.\n",
    "- `ds.where(<selector_reduction>, lookup_column)` can be used to extract the values of another column based on a selector refuction (e.g. `'first'`, `'min'`). For example, setting `aggregator=ds.where(ds.min('s'), 'val')` will display in each pixel the value of the variable `'val'` where the variable `'s'` is minimum.\n",
    "\n",
    "Let's start with the simple case, setting aggregator with a plain string and with a datashader reduction object."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "import datashader as ds\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='x', y='y', data_aspect=1, frame_height=250)\n",
    "df.hvplot.points(\n",
    "    rasterize=True, aggregator='var', color='s',\n",
    "    clabel='var(s)', **plot_opts\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    rasterize=True, aggregator=ds.min('s'), clabel='min(s)',\n",
    "    **plot_opts\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The example below shows how `aggregator` can be used to handle categorical data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "import datashader as ds\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='x', y='y', data_aspect=1, frame_height=250)\n",
    "df.hvplot.points(\n",
    "    datashade=True, aggregator=ds.by('cat'),\n",
    "    title=\"Categorical datashading with\\n'count' aggregator'\", **plot_opts\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    datashade=True, aggregator=ds.by('cat', ds.min('s')), hover_cols=['s'],\n",
    "    title=\"Categorical datashading with\\n'min(s)' aggregator'\", **plot_opts\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The next examples show how to leverage `ds.summary()` and `ds.where()`. Hover over the plots to see how what information is made available in the tooltip."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "import datashader as ds\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='x', y='y', data_aspect=1, frame_height=250)\n",
    "df.hvplot.points(\n",
    "    rasterize=True, title=\"summary(\\n  min_s=min('s'), max_s=max('s')\\n)\",\n",
    "    aggregator=ds.summary(min_s=ds.min('s'), min_val=ds.min('val')),\n",
    "    **plot_opts\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    rasterize=True, title=\"where(min('s', 'val')\",\n",
    "    aggregator=ds.where(ds.min('s'), 'val'),\n",
    "    **plot_opts\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-datashade)=\n",
    "## `datashade`\n",
    "\n",
    "The `datashade` option can be used to apply rasterization (aggregation into a grid of pixels) and colormapping operations using the [Datashader](https://datashader.org) library. Enabling this options allows:\n",
    "- Rendering large datasets in the browser that would otherwise crash it as it can easily handle billions of data points.\n",
    "- Dynamically exploring large datasets and discovering patterns, which would otherwise be difficult to find as plotting large data come with [many pitfalls](https://datashader.org/user_guide/Plotting_Pitfalls.html) such as overplotting.\n",
    "\n",
    "This approach can turn even the largest datasets into an image that captures patterns such as density or value distribution, making it ideal for high-volume scatter plots. When `datashade=True`, hvPlot returns a [`DynamicMap`](inv:holoviews#reference/containers/bokeh/DynamicMap) containing an [`RGB`](inv:holoviews#reference/elements/bokeh/RGB) instead of individual glyphs.\n",
    "\n",
    ":::{tip}\n",
    "Since `datashade=True` produces an RGB image, the underlying data (e.g. the aggregated values per pixel) is not directly available to the plot. Enabling the `'hover'` [tool](option-hover) (disabled by default when `datashade=True` unless [`selector`](option-selector) is set) would only show the RGB value per pixel, and no meaningful colorbar can be attached to the plot. To let the frontend apply colormapping instead of the backend, and as a consequence expose the underlying data, we recommend setting [`rasterize=True`](option-rasterize) instead of `datashade=True`.\n",
    ":::\n",
    "\n",
    "The [`cnorm`](option-cnorm) option defaults to `'eq_hist'` when `datashade=True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='x', y='y', datashade=True, data_aspect=1, frame_height=250,\n",
    "    title='Datashaded points plot with\\n\"count\" aggregator and\\n\"eq_hist\" cnorm'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this example, the entire dataset is rasterized into a colormapped image, with denser areas appearing darker."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-downsample)=\n",
    "## `downsample`\n",
    "\n",
    "The `downsample` option can be used to dynamically reduce the number of plotted points by summarizing data before rendering, making it ideal for large timeseries datasets. This results in lighter plots and faster rendering.\n",
    "\n",
    "Valid values include (most require the [`tsdownsample`](https://github.com/predict-idlab/tsdownsample) library to be installed):\n",
    "\n",
    "- `False`: No downsampling is applied.\n",
    "- `True`: Applies downsampling using HoloViews' default algorithm LTTB.\n",
    "- `'lttb'`: Explicitly applies the [Largest Triangle Three Buckets algorithm (LTTB)](https://skemman.is/handle/1946/15343). Uses `tsdownsample` if installed, if not defers to HoloViews' LTTB implementation (slower).\n",
    "- `'minmax'`: Applies the MinMax algorithm, selecting the minimum and maximum values in each bin. Requires ``tsdownsample``.\n",
    "- `'m4'`: Applies the M4 algorithm, selecting the minimum, maximum, first, and last values in each bin. Requires ``tsdownsample``.\n",
    "- `'minmax-lttb'`: Combines MinMax and LTTB algorithms for downsampling, first applying MinMax to reduce to a preliminary set of points, then LTTB for further reduction. Requires `tsdownsample`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = (\n",
    "    hvplot.sampledata.stocks(\"pandas\")\n",
    "    .set_index(\"date\")[[\"Apple\"]]\n",
    "    .resample(\"2Min\")\n",
    "    .interpolate(method=\"polynomial\", order=5)\n",
    ")\n",
    "print(f\"This dataset contains {len(df)} rows.\")\n",
    "\n",
    "df.hvplot.line(downsample=\"lttb\", width=500, height=300, title=\"downsampled with lttb\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-dynspread)=\n",
    "## `dynspread`\n",
    "\n",
    "When rendering with [`datashade=True`](option-datashade) or [`rasterize=True`](option-datashade), individual points can become hard to see, especially when sparse (1 isolated data point will color 1 pixel only which can be hard to see on a screen). Enabling `dynspread=True` dynamically increases the size of points in less dense areas, making them more visible.\n",
    "\n",
    "In more details, spreading expands each pixel a certain number of pixels on all sides according to a circular shape, merging pixels using a compositing operator. Dynamic spreading determines how many pixels to spread based on a density heuristic. Spreading starts at 1 pixel, and stops when the fraction of adjacent non-empty pixels reaches the specified [`threshold`](option-threshold), or the [`max_px`](option-max_px) is reached, whichever comes first.\n",
    "\n",
    "In the example below, we zoom in over an area with sparse data. The colored pixels are difficult to distinguish on the left plot, while they are clearly visible on the right plot with `dynspread=True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "plot_opts = dict(\n",
    "    x='x', y='y', frame_height=250, data_aspect=1,\n",
    "    xlim=(-5.5, -5), ylim=(2.5, 3),\n",
    ")\n",
    "df.hvplot.points(\n",
    "    rasterize=True, dynspread=False,\n",
    "    title=\"Datashade without dynspread\", **plot_opts,\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    rasterize=True, dynspread=True,\n",
    "    title=\"Datashade with dynspread\", **plot_opts,\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-max_px)=\n",
    "## `max_px`\n",
    "\n",
    "The `max_px` option sets the upper limit on how much [`dynspread`](option-dynspread) can increase point size (in pixels, default is `3`). It only applies when `dynspread=True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "plot_opts = dict(\n",
    "    x='x', y='y', frame_height=250, data_aspect=1,\n",
    "    xlim=(-5.5, -5), ylim=(2.5, 3),\n",
    ")\n",
    "df.hvplot.points(\n",
    "    rasterize=True, dynspread=True,\n",
    "    title=\"Dynspread with max_px=3 (default)\", **plot_opts,\n",
    ") +\\\n",
    "df.hvplot.points(\n",
    "    rasterize=True, dynspread=True, max_px=8,\n",
    "    title=\"Dynspread with max_px=8\", **plot_opts\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    ":::{note}\n",
    "Larger values make sparse data more prominent but can also distort the visual scale if overused.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-pixel_ratio)=\n",
    "## `pixel_ratio`\n",
    "\n",
    "This option adjusts the internal pixel resolution used by [`datashade`](option-datashade) or [`rasterize`](option-rasterize), relative to the actual display size.\n",
    "\n",
    ":::{note}\n",
    "By default, and if possible, `pixel_ratio` is inferred automatically from the browser and will be internally set to `2` on high-DPI screens to improve sharpness. You can override it manually for consistent rendering across devices.\n",
    ":::"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='x', y='y', datashade=True, pixel_ratio=0.1, frame_height=250,\n",
    "    data_aspect=1, title=\"Datashade with low pixel ratio\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-precompute)=\n",
    "## `precompute`\n",
    "\n",
    "Operations that involve rasterization ([`rasterize`](option-rasterize) and [`datashade`](option-datashade)) can be computationally expensive as they operate on the full dataset (unlike e.g. [`dynspread`](option-dynspread)). `precompute` can be set to `True` to get faster performance in interactive usage by caching the last set of data used in plotting (*after* any transformations needed) and reusing it when it is requested again. This is particularly useful when your data is not in one of the supported data formats already and needs to be converted. `precompute` is `False` by default, because it requires using memory to store the cached data, but if you have enough memory, you can enable it so that repeated interactions (such as zooming and panning) will be much faster than the first one. Learn more about this option in the [HoloViews large data user guide](https://holoviews.org/user_guide/Large_Data.html#cache-initial-processing-with-precompute-true).\n",
    "\n",
    ":::{tip}\n",
    "In practice, most Datashader-plots don't need to do extensive precomputing, but enabling it for {meth}`hvplot.hvPlot.polygons` and {meth}`hvplot.hvPlot.quadmesh` plots can greatly speed up interactive usage.\n",
    ":::"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-rasterize)=\n",
    "## `rasterize`\n",
    "\n",
    "The `rasterize` option can be used to apply a rasterization (aggregation into a grid of pixels) operation using the [Datashader](https://datashader.org) library. Enabling this options allows:\n",
    "- Rendering large datasets in the browser that would otherwise crash it as it can easily handle billions of data points.\n",
    "- Dynamically exploring large datasets and discovering patterns, which would otherwise be difficult to find as plotting large data come with [many pitfalls](https://datashader.org/user_guide/Plotting_Pitfalls.html) such as overplotting.\n",
    "\n",
    "This approach can turn even the largest datasets into an image that captures patterns such as density or value distribution, making it for example ideal for high-volume scatter plots. This option applies only rasterization, leaving colormapping to the plotting backend. Unlike [`datashade`](option-datashade), the returned [`DynamicMap`](inv:holoviews#reference/containers/bokeh/DynamicMap) does not contain an `RGB` element but data grid ([`Image`](inv:holoviews#reference/elements/bokeh/Image) or [`ImageStack`](inv:holoviews#reference/elements/bokeh/ImageStack)). This allows exposing the underlying data to the user interface via for example a colorbar and additional values displayed in the hover tooltip.\n",
    "\n",
    "The [`cnorm`](option-cnorm) option defaults to `'linear'` when `datashade=True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='x', y='y', rasterize=True, data_aspect=1, frame_height=250, cnorm='log',\n",
    "    title='Rasterized points with count aggregator\\nand log cnorm'\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "In this example, the entire dataset is rasterized into an image grid that is colormapped in the front-end, with denser areas appearing darker. Hover over the plot to see the count computed in each bin/pixel."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-resample_when)=\n",
    "## `resample_when`\n",
    "\n",
    "Operations like [`rasterize`](option-rasterize), [`datashade`](option-datashade), and [`downsample`](option-downsample) are very effective at displaying large datasets. When interacting with a plot, and for example zooming in over a region with a few points, these operations are in practice no longer needed. `resample_when` can be set to a number of data points present in the viewport below which resampling is toggled off. "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='x', y='y', rasterize=True, resample_when=1_000,\n",
    "    data_aspect=1, frame_height=250, cnorm='log',\n",
    "    title=\"Rasterize only when >1000 points in view\"\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "When running the code above, you will notice that after zooming in enough, the original data points appear. This gives a hybrid experience: raw points at low density, rasterized aggregates when zoomed out."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-selector)=\n",
    "## `selector`\n",
    "\n",
    ":::{versionadded} 0.12.0\n",
    "Requires `holoviews>=1.21`.\n",
    "Requires `bokeh>=3.7`.\n",
    ":::\n",
    "\n",
    "When a Datashader operation is applied, with [`datashade=True`](option-datashade) or [`rasterize=True`](option-rasterize), the `selector` option allows to augment the tooltip with information computed (*selected*) from variables other than the aggregated one, effectively showing a sample of the dataset in the tooltip.\n",
    "\n",
    "Datashader operations allow to easily identify *macro level patterns* in large datasets by aggregating the data appropriately. However, they do not by default expose information about *individual data points*. Let's take for example a simple scatter plots set with `rasterize=True`; hovering over the image will only display the aggregated value per pixel (`'count'` by default), with no way to know more about each point (unless [`resample_when`](option-resample_when) is enabled and the user zooms in enough). Setting `selector` in this case would augment the tooltip with sample information from other variables, selected from *one unique row* of the dataset. Find out more about `selector` in HoloViews' [Interactive Hover for Big Data guide](https://dev.holoviews.org/user_guide/Interactive_Hover_for_Big_Data.html).\n",
    "\n",
    "Like the [`aggregator`](option-aggregator) option, a `selector` refers to a [Datashader `Reduction` object](https://datashader.org/api.html#reductions). However, unlike `aggregator` that accepts reductions that can combine data in a pixel (e.g. `'mean'` or `'count'`), `selector` only accepts reductions that *select* values, including: `'first'`, `'last'`, `'min'`, and `'max'`. Valid options include:\n",
    "- A string object for reductions that do not require a variable name, including `'first'` and `'last'`.\n",
    "- A 2-tuple with a reduction name and a variable name, for reductions that require a variable name, including `'min'` and `'max'` (e.g. `('min', 'column')`).\n",
    "- A reduction instance, including `ds.first()`, `ds.last()`, `ds.min()`, and `ds.max()`.\n",
    "\n",
    "::: {note}\n",
    "The hover tooltip always requires a live kernel when `selector` is set as the values displayed need to be sent by the Python server. Without a live kernel, like on this webpage, all the values are displayed as `'undefined'`.\n",
    ":::\n",
    "\n",
    "When you hover over the first plot below, you will see a value for `s`, `val`, and `cat` in the bottom part of the tooltip. All these values originate from the same row in the DataFrame, that row being the first one found in the subdataset contained within this pixel. In the second plot, the values displayed are derived from the row where `val` is minimum within the hovered pixel."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "plot_opts = dict(x='x', y='y', rasterize=True, data_aspect=1, frame_height=250, cnorm='log')\n",
    "(\n",
    "    df.hvplot.points(selector='first', title='selector=\"first\"', **plot_opts) +\n",
    "    df.hvplot.points(selector=('min', 'val'), title='selector=(\"min\", \"val\")', **plot_opts)\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`datashade=True` plots get their hover tool enabled by default when `selector` is set."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import datashader as ds\n",
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='x', y='y', data_aspect=1, frame_height=250, cnorm='log',\n",
    "    datashade=True, selector=ds.min('val'), title='datashade=True',\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`selector` can also be set when datashading categorical data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import datashader as ds\n",
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='x', y='y', data_aspect=1, frame_height=250, colorbar=False,\n",
    "    rasterize=True, aggregator=ds.by('cat'), selector='first',\n",
    "    title=\"Categorical rasterizing with\\n'count' aggregator'\",\n",
    ")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-threshold)=\n",
    "## `threshold`\n",
    "\n",
    "Controls sensitivity for [`dynspread`](option-dynspread). A value of `1.0` always spreads sparse points, while `0.0` never does. Intermediate values let you tune spreading behavior. It only applies when `dynspread=True`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "plot_opts = dict(\n",
    "    x='x', y='y', datashade=True, dynspread=True,\n",
    "    data_aspect=1, frame_width=200, xlim=(-2, 0), ylim=(7, 9),\n",
    ")\n",
    "df.hvplot.points(threshold=0.0, title=\"Dynspread threshold=0.0\", **plot_opts) +\\\n",
    "df.hvplot.points(threshold=0.5, title=\"Dynspread threshold=0.5\", **plot_opts) +\\\n",
    "df.hvplot.points(threshold=1.0, title=\"Dynspread threshold=1.0\", **plot_opts)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "(option-x_sampling__y_sampling)=\n",
    "## `x_sampling` / `y_sampling`\n",
    "\n",
    "Set minimum data resolution in the x and/or y direction when setting [`datashade`](option-datashade) or [`rasterize`](option-rasterize). This is useful to set the granularity of the pixel grid when zoomed in, or when visualizing images or gridded data that requires consistent resolution control."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import hvplot.pandas  # noqa\n",
    "\n",
    "df = hvplot.sampledata.synthetic_clusters(\"pandas\")\n",
    "\n",
    "df.hvplot.points(\n",
    "    x='x', y='y', rasterize=True, x_sampling=0.1, y_sampling=0.1,\n",
    "    data_aspect=1, cnorm='log', xlim=(0, 1), ylim=(0, 1), frame_height=250,\n",
    "    title='Zoomed in rasterized plot\\nwith custom x/y-sampling'\n",
    ")"
   ]
  }
 ],
 "metadata": {
  "language_info": {
   "name": "python",
   "pygments_lexer": "ipython3"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
